package com.tomecode.mjprocessor.assembly;

import java.io.File;
import java.io.IOException;

import javax.xml.XMLConstants;
import javax.xml.validation.Validator;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.tomecode.mjprocessor.assembly.action.Action;
import com.tomecode.mjprocessor.assembly.action.ActionType;
import com.tomecode.mjprocessor.assembly.type.ProjectActions;
import com.tomecode.mjprocessor.assembly.type.ProcessType;

/**
 * 
 * Parse assembly descriptor
 * 
 * @author Frastia Tomas
 * 
 */
public final class AssemblyReader {

	private final File baseDir;
	private final DocumentBuilderFactory documentBuilderFactory;

	private final Validator validator;

	public AssemblyReader(File baseDir) throws SAXException {
		this.baseDir = baseDir;
		documentBuilderFactory = DocumentBuilderFactory.newInstance();
		documentBuilderFactory.setNamespaceAware(true); // never forget this!
		SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

		Source schemaFile = new StreamSource(AssemblyReader.class.getResourceAsStream("maven-java-processor.xsd"));
		Schema schema = factory.newSchema(schemaFile);
		validator = schema.newValidator();
	}

	/**
	 * parse assembly file and validate
	 * 
	 * @param assemblyFile
	 * @return
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 */
	public final Assembly parse(File assemblyFile) throws ParserConfigurationException, SAXException, IOException {
		Document document = documentBuilderFactory.newDocumentBuilder().parse(assemblyFile);
		validator.validate(new DOMSource(document));
		return parseProjects(document.getDocumentElement().getChildNodes());
	}

	/**
	 * parse project element
	 * 
	 * @param childNodes
	 * @return
	 */
	private final Assembly parseProjects(NodeList childNodes) {
		Assembly assembly = new Assembly();

		for (int i = 0; i <= childNodes.getLength() - 1; i++) {
			Node node = childNodes.item(i);
			if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals("project")) {
				assembly.addProject(parseProject(node));
			}
		}
		return assembly;
	}

	/**
	 * parse tag {@link Project}
	 * 
	 * @param nodeProject
	 * @return
	 */
	private final Project parseProject(Node nodeProject) {

		NamedNodeMap attr = nodeProject.getAttributes();
		Project project = new Project(attr.getNamedItem("groupId").getNodeValue(), attr.getNamedItem("artifactId").getNodeValue());

		NodeList nodeList = nodeProject.getChildNodes();
		for (int i = 0; i <= nodeList.getLength() - 1; i++) {
			Node node = nodeList.item(i);
			if (node.getNodeType() == Node.ELEMENT_NODE) {
				if (node.getNodeName().equals("xml")) {
					project.addTypes(parseProcessorType(node, ProcessType.XML));
				} else if (node.getNodeName().equals("properties")) {
					project.addTypes(parseProcessorType(node, ProcessType.PROPERTIES));
				}
			}
		}

		return project;
	}

	/**
	 * parse processor description
	 * 
	 * @param nodeRoot
	 * @param type
	 *            {@link ProcessType}
	 * @return
	 */
	private final ProjectActions parseProcessorType(Node nodeRoot, ProcessType type) {
		ProjectActions processor = new ProjectActions(type);

		// parse prifile name
		NamedNodeMap namedNodeMap = nodeRoot.getAttributes();
		for (int i = 0; i <= namedNodeMap.getLength() - 1; i++) {
			Node attr = namedNodeMap.item(i);
			if (attr.getNodeName().equals("profile")) {
				processor.setProfile(attr.getTextContent().trim());
			}
		}

		NodeList nodeList = nodeRoot.getChildNodes();
		for (int i = 0; i <= nodeList.getLength() - 1; i++) {
			Node node = nodeList.item(i);
			if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals("target-file")) {
				processor.setTargetFile(createFile(node.getTextContent().trim()));
			} else if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals("source-file")) {
				processor.setSourceFile(createFile(node.getTextContent().trim()));
			} else if (node.getNodeType() == Node.ELEMENT_NODE) {
				processor.addAction(parseAction(node));
			}
		}
		return processor;
	}

	/**
	 * convert string file to file
	 * 
	 * @param fileInDescription
	 * @return
	 */
	private final File createFile(String fileInDescription) {
		if (fileInDescription.startsWith("/") || fileInDescription.startsWith("\\")) {
			return new File(baseDir + fileInDescription);
		}
		return new File(baseDir + File.separator + fileInDescription);
	}

	/**
	 * parse action node (xml or properties)
	 * 
	 * @param node
	 * @return {@link Action}
	 */
	private final Action parseAction(Node node) {
		Action action = new Action(ActionType.parse(node.getNodeName()));

		NodeList nodeList = node.getChildNodes();
		for (int i = 0; i <= nodeList.getLength() - 1; i++) {
			Node nodeChild = nodeList.item(i);
			if (nodeChild.getNodeType() == Node.ELEMENT_NODE) {
				if (nodeChild.getNodeName().equals("value")) {
					action.setValue(nodeChild.getTextContent());
				} else if (nodeChild.getNodeName().equals("xpath")) {
					action.setXPath(nodeChild.getTextContent());
				} else if (nodeChild.getNodeName().equals("name")) {
					action.setPropertyName(nodeChild.getTextContent());
				}
			}
		}

		return action;
	}
}
